#include"dir.h"
#include"memory.h"
#include"string.h"
#include"inode.h"
#include"stdio_kernal.h"
#include"bitmap.h"
#include"debug.h"

struct dir root;
extern struct partition* curr_part;

static bool is_dir_empty(struct dir*);
static int32_t rm_dir(struct partition* part,struct dir* pdir,char* name,int32_t i_no);

void open_root_dir(struct partition* part){
	root.inode=open_inode(part,part->sb->root_ino);
	root.dir_pos=0;
}

struct dir* open_dir(struct partition* part,uint32_t i_no){
	struct dir* pdir;
	pdir=sys_malloc(sizeof(struct dir));
	pdir->inode=open_inode(part,i_no);
	pdir->dir_pos=0;
	return pdir;
}

void close_dir(struct dir* pdir){
	if(pdir==&root){
		return;
	}
	close_inode(pdir->inode);
	sys_free(pdir);
}

void create_dir_entry(const char* name,uint32_t i_no,fs_type ft,struct dir_entry* dir_e){
	strcpy(dir_e->name,name);
	dir_e->i_no=i_no;
	dir_e->ft=ft;
}

static bool insert_dir_entry(uint32_t* pblocks,uint32_t b_cnt,struct partition* part,struct dir* pdir,struct dir_entry* dir_e,char* iobuffer){
	uint32_t b_idx=0;
	uint32_t e_idx=0;
	int32_t b_lba;
	struct dir_entry* pe;

	while(b_idx<b_cnt){
		if(pblocks[b_idx]){
			ide_read(part->disk,iobuffer,pblocks[b_idx],1);
			pe=(struct dir_entry*)iobuffer;
			e_idx=0;
			while(e_idx<DIR_ENTRY_PER_SECTOR){
				if(pe[e_idx].name[0]==0){
					memcpy(pe+e_idx,dir_e,sizeof(struct dir_entry));
					ide_write(part->disk,iobuffer,pblocks[b_idx],1);
					pdir->inode->pure_node.i_size+=sizeof(struct dir_entry);
					inode_sync(part,pdir->inode,iobuffer);
					return true;
				}
				e_idx++;
			}
		}else{
			b_lba=fs_bitmap_alloc(part,FS_BLOCK_BITMAP);
			if(b_lba<0){
				return false;
			}
			fs_bitmap_sync(part,b_lba,FS_BLOCK_BITMAP);
			b_lba+=part->sb->free_block_lba;
			pblocks[b_idx]=b_lba;
			memset(iobuffer,0,BLOCK_SIZE);
			pe=(struct dir_entry*)iobuffer;
			memcpy(pe,dir_e,sizeof(struct dir_entry));
			ide_write(part->disk,iobuffer,pblocks[b_idx],1);
			pdir->inode->pure_node.i_size+=sizeof(struct dir_entry);
			inode_sync(part,pdir->inode,iobuffer);
			return true;
		}
		b_idx++;
	}
	return false;
}

static bool insert_dir_entry_in_empty_indirect_block(uint32_t* pblocks,struct partition* part,struct dir* pdir,struct dir_entry* dir_e,char* iobuffer){
	int32_t b_lba;
	struct dir_entry* pe;

	b_lba=fs_bitmap_alloc(part,FS_BLOCK_BITMAP);
	if(b_lba<0){
		return false;
	}
	fs_bitmap_sync(part,b_lba,FS_BLOCK_BITMAP);
	b_lba+=part->sb->free_block_lba;
	*pblocks=b_lba;
	memset(iobuffer,0,BLOCK_SIZE);
	pe=(struct dir_entry*)iobuffer;
	memcpy(pe,dir_e,sizeof(struct dir_entry));
	ide_write(part->disk,iobuffer,*pblocks,1);
	inode_sync(part,pdir->inode,iobuffer);
	return true;
}

bool dir_entry_insert_sync(struct partition* part,struct dir* pdir,struct dir_entry* dir_e,char* iobuffer){
	uint32_t* pblocks;
	pblocks=pdir->inode->pure_node.blocks;
	//try intert empty dir enty in dirct block	
	if(insert_dir_entry(pblocks,INODE_BLOCK_CNT-1,part,pdir,dir_e,iobuffer)){
		return true;	
	}
	//indirect block is empty
	if(!pblocks[INODE_BLOCK_CNT-1]){
		return insert_dir_entry_in_empty_indirect_block(&pblocks[INODE_BLOCK_CNT-1],part,pdir,dir_e,iobuffer);
	}
	//try find empty dir entry in indirect block
	pblocks=sys_malloc(SECTOR_SIZE);	
	if(!pblocks){
		return false;
	}
	ide_read(part->disk,(char*)pblocks,pdir->inode->pure_node.blocks[INODE_BLOCK_CNT-1],1);
	if(insert_dir_entry(pblocks,BLOCK_INDEX_CNT_PER_SECTOR,part,pdir,dir_e,iobuffer)){
		return true;
	}
	sys_free(pblocks);
	printk("dirctory is full\n");
	return false;
}

//delete the filenam dir entry and sync
bool dir_entry_delete_sync(struct partition* part,struct dir* pdir,const char* filename,char* iobuffer){
	
	uint32_t b_idx=0;
	uint32_t e_idx=0;
	bool found=false;
	struct dir_entry* pentrys;
	uint32_t* pblocks; 
	uint32_t block_lba;
	uint32_t block_entry_cnt;
	uint32_t indirect_block_cnt;
	uint32_t found_b_idx;
	uint32_t found_e_idx;
	
	//try remove filename dir entry in direct block	
	pblocks=pdir->inode->pure_node.blocks;
	while(b_idx<INODE_BLOCK_CNT-1){
		block_lba=pblocks[b_idx];
		if(block_lba){
			ide_read(part->disk,iobuffer,block_lba,1);
			block_entry_cnt=0;
			pentrys=(struct dir_entry*)iobuffer;
			e_idx=0;
			while(e_idx<DIR_ENTRY_PER_SECTOR){
				if(pentrys[e_idx].name[0]!=0){
					if(!strcmp(pentrys[e_idx].name,filename)){
					       	found=true;
						found_e_idx=e_idx;
						found_b_idx=b_idx;
					}
					block_entry_cnt++;
				}
				e_idx++;
			}
			//first direct block should always not be remove
			if(found && block_entry_cnt==1){
				bitmap_set(&part->block_bitmap,pblocks[found_b_idx]-part->sb->free_block_lba,0);
				fs_bitmap_sync(part,pblocks[found_b_idx]-part->sb->free_block_lba,FS_BLOCK_BITMAP);
				pblocks[found_b_idx]=0;
				inode_sync(part,pdir->inode,iobuffer);
				return true;
			}else if(found){
				memset(pentrys+found_e_idx,0,sizeof(struct dir_entry));
				ide_write(part->disk,iobuffer,pblocks[found_b_idx],1);
				return true;
			}
		}
		b_idx++;
	}
	//try remove filename dir entry in indirect block
	block_lba=pblocks[INODE_BLOCK_CNT-1];
	if(block_lba){
		e_idx=0;
		b_idx=0;
		found=false;
		indirect_block_cnt=0;
		pblocks=sys_malloc(SECTOR_SIZE);
		ide_read(part->disk,(char*)pblocks,block_lba,1);
		
		while(b_idx<BLOCK_INDEX_CNT_PER_SECTOR){
			block_lba=pblocks[b_idx];
			if(block_lba){
				if(!found){
					pentrys=(struct dir_entry*)iobuffer;
					ide_read(part->disk,iobuffer,block_lba,1);
					block_entry_cnt=0;
					e_idx=0;
					while(e_idx<DIR_ENTRY_PER_SECTOR){
						if(pentrys[e_idx].name[0]!=0){
							if(!strcmp(pentrys[e_idx].name,filename)) {
								found=true;
								found_e_idx=e_idx;
								found_b_idx=b_idx;
							}
							block_entry_cnt++;
						}
						e_idx++;
					}
				}
				indirect_block_cnt++;
			}else if(found && indirect_block_cnt>1){
				break;
			}
			b_idx++;
		}
		//indirect has only one block and the only block has only one entry so we can
		//release the indirect block now
		if(found && indirect_block_cnt==1 && block_entry_cnt==1){
			bitmap_set(&part->block_bitmap,\
			pdir->inode->pure_node.blocks[INODE_BLOCK_CNT-1]-part->sb->free_block_lba,0);
			fs_bitmap_sync(part,\
					pdir->inode->pure_node.blocks[INODE_BLOCK_CNT-1]-part->sb->free_block_lba,\
					FS_BLOCK_BITMAP);
			pdir->inode->pure_node.blocks[INODE_BLOCK_CNT-1]=0;
			inode_sync(part,pdir->inode,iobuffer);
		//indrect has more than one block we can release the block which the entry we found in
		}else if(found && indirect_block_cnt>1 && block_entry_cnt==1){
			bitmap_set(&part->block_bitmap,\
					pblocks[found_b_idx]-part->sb->free_block_lba,0);
			fs_bitmap_sync(part,pblocks[found_b_idx]-part->sb->free_block_lba,\
					FS_BLOCK_BITMAP);
			pblocks[found_b_idx]=0;
			ide_write(part->disk,(char*)pblocks,pdir->inode->pure_node.blocks[INODE_BLOCK_CNT-1],1);
		}else if(found){
		//block has more one entrys
			memset(pentrys+found_e_idx,0,sizeof(struct dir_entry));	
			ide_write(part->disk,iobuffer,pblocks[found_b_idx],1);
		}
		sys_free(pblocks);
		if(found) return true;
	}
	return false;
}	


void get_dir_entry(struct partition* part,char* iobuffer,struct winode* wi,struct dir_entry* dir_e,int32_t i_no){
	uint32_t all_blocks[INODE_BLOCK_CNT-1+BLOCK_INDEX_CNT_PER_SECTOR]={0};
	uint32_t b_idx=0;
	uint32_t e_idx=0;
	struct dir_entry* pentrys;
	if(i_no>0){
		node_all_block(part,&wi->pure_node,all_blocks,iobuffer);
		while(b_idx<INODE_BLOCK_CNT-1+BLOCK_INDEX_CNT_PER_SECTOR && all_blocks[b_idx]!=0){
			ide_read(part->disk,iobuffer,all_blocks[b_idx],1);
			pentrys=(struct dir_entry*)iobuffer;
			e_idx=0;
			while(e_idx<DIR_ENTRY_PER_SECTOR && pentrys[e_idx].name[0]){
				if((uint32_t)i_no==pentrys[e_idx].i_no){
					memcpy(dir_e,pentrys+e_idx,sizeof(struct dir_entry));
					return;	
				}
				e_idx++;
			}
			b_idx++;
		}
	}	
}


void get_parent_dir_entry(struct dir_entry* all_entrys,struct dir_entry* dir_e,uint32_t cnt){
	uint32_t e_idx=0;
	while(e_idx<cnt){
		if(!strcmp(all_entrys[e_idx].name,"..")){
			memcpy(dir_e,all_entrys+e_idx,sizeof(struct dir_entry));
		}
		e_idx++;
	}
}

static struct dir_entry* readdir(struct partition* part,struct dir* dir){
	uint32_t all_blocks[NODE_ALL_BLOCK_CNT]={0};
	uint32_t b_idx=0;
	uint32_t e_idx=0;
	uint32_t block_cnt;
	uint32_t curr_pos=0;

	block_cnt=dir->dir_pos/BLOCK_SIZE;
	node_all_block(part,&dir->inode->pure_node,all_blocks,(char*)dir->dir_entrys);
	
	while(b_idx<NODE_ALL_BLOCK_CNT && all_blocks[b_idx]!=0){
		if(b_idx<block_cnt){
			b_idx++;
			curr_pos+=DIR_ENTRY_PER_SECTOR;
			continue;
		}
		ide_read(part->disk,(char*)dir->dir_entrys,all_blocks[b_idx],1);
		e_idx=0;
		while(e_idx<DIR_ENTRY_PER_SECTOR){
			if(dir->dir_entrys[e_idx].ft){
				if(curr_pos<dir->dir_pos){
					curr_pos++;
					e_idx++;
					continue;
				}
				return &dir->dir_entrys[dir->dir_pos++];
			}
			e_idx++;
		}
		b_idx++;
	}
	return NULL;
}

struct dir_entry* sys_readdir(struct dir* dir){
	ASSERT(dir);
	return readdir(curr_part,dir);	
}

struct dir* sys_opendir(const char* path){
	path_record pr;
	int32_t i_no;
	struct dir* d=NULL;
	char* filename;
	i_no=path_parse(path,&pr);
	filename=strrchr(pr.path,'/')+1;
	if(i_no<0){
		printk("%s not exist \n",filename);
	}else if(pr.ft==FT_NORMAL){
		printk("%s is a normal file\n",filename);
	}else if(!pr.is_path_valid){
		printk("%s not invalide\n",pr.path);
	}else{
		d=open_dir(curr_part,i_no);
	}
	close_dir(pr.pdir);
	return d;
}


static bool is_dir_empty(struct dir* dir){
	return dir->inode->pure_node.i_size==sizeof(struct dir_entry)*2 ? true : false;
}

int32_t sys_closedir(struct dir* dir){
	if(dir!=NULL){
		close_dir(dir);
		return 0;
	}
	return -1;
}

void sys_rewinddir(struct dir* dir){
	if(dir!=NULL){
		dir->dir_pos=0;
	}
}

static int32_t rm_dir(struct partition* part,struct dir* pdir,char* name,int32_t i_no){
	struct dir* dir;
	char* iobuffer;
	uint32_t error_stat;

	dir=open_dir(part,i_no);
	if(!dir){
		return -1;
	}

	if(!is_dir_empty(dir)){
		error_stat=1;
		printk("dirctory is not empty\n");
		goto error_handle;	
	}
	
	//release dirctory inode
	iobuffer=sys_malloc(BLOCK_SIZE);
	if(!release_inode(part,i_no,iobuffer)){
		goto error_handle;
	}
	
	//delete parent dir entry
	if(!dir_entry_delete_sync(part,pdir,name,iobuffer)){
		goto error_handle;
	}
	close_dir(dir);
	sys_free(iobuffer);
	return 0;
error_handle:
	switch(error_stat){
		case 1:close_dir(dir);
	}
	sys_free(iobuffer);
	return -1;
}

int32_t sys_rmdir(const char* path){
	path_record pr;
	int32_t i_no;	
	char* filename;
	int32_t ret=-1;
	
	i_no=path_parse(path,&pr);
	filename=strrchr(pr.path,'/')+1;
	if(i_no<0){
		printk("%s not exist\n",filename);
	}else if(pr.ft==FT_NORMAL){
		printk("%s is a normal file\n",filename);
	}else if(!pr.is_path_valid){
		printk("%s not invalide\n",pr.path);
	}else{
		ret=rm_dir(curr_part,pr.pdir,filename,i_no);
	}
	close_dir(pr.pdir);
	return ret;
}
